/**
 * 
 */
package roborally.model.items;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Random;
import be.kuleuven.cs.som.annotate.*;
import be.kuleuven.cs.som.taglet.*;

import roborally.model.*;

/**
 * A class of surprise boxes. 
 * When a robot uses a surprise box, one of the following events occur (selected at random): 
 * the surprise box explodes and the robot is hit, the robot is teleported to a random position, 
 * or the surprise box contains another item (either a battery, a repair kit or a new surprise box) 
 * and this item is added to the list of items carried by the robot. A surprise box can only be used once.
 * 
 * @author Mathieu REYMOND
 * @author Joren VERSPEURT
 *
 */
public class SurpriseBox extends Item {

	/**
	 * Creates a Surprise Box with a given weight.
	 * 
	 * @param weight The given weight.
	 */
	public SurpriseBox(int weight) {
		super(weight);
	}

	/**
	 * Invoked when a robot uses this Surprise Box. When invoked it executes a random event method.
	 * Currently events include: exploding (damages the Robot this item belongs to), teleporting the Robot to a random position on the board,
	 * returning a random item which is then added to the Robot's possessions.
	 * Invoking this method always ends in the termination of the Surprise Box.
	 * 
	 * @param robot The robot that uses this item.
	 * 
	 * @effect ...
	 * 		   | Let randInt = new Random().nextInt(3)
	 * 		   | In
	 * 		   | 	if randInt == 0
	 * 		   |	then eventExplode(robot)
	 * 		   | 	if randInt == 1
	 * 		   | 	then eventTeleport(robot)
	 * 		   | 	if randInt == 2
	 * 		   | 	then eventRandomItem(robot)
	 * 		   | 	this.terminate()
	 */
	@Override
	public void use(Robot robot) throws IllegalArgumentException{
		if(!robot.getItems().contains(this)) throw new IllegalArgumentException("The given robot is not holding this item.");
		
		Random random = new Random();
		Method[] allMethods = this.getClass().getDeclaredMethods();
		ArrayList<Method> methods = new ArrayList<Method>();
		for(Method m : allMethods) {
			if(m.getName().startsWith("event")) {
				methods.add(m);
			}
				
		}
		try {
			int randInt = random.nextInt(methods.size());
			methods.get(randInt).invoke(this, robot);
		} catch (Exception e) {
			e.printStackTrace();
		}
		
		terminate();
	}
	
	/**
	 * The surprise box explodes and damages the given robot (which should be the robot carrying it)
	 * @effect ...
	 * 		   | robot.takeshot()
	 */
	@Model
	private void eventExplode(Robot robot){
		robot.takeShot();
	}

	/**
	 * Teleports the given robot to a random position on the robot's board.
	 * 
	 * @param robot The given robot.
	 * 
	 * @effect ...
	 * 		   | Let
	 * 		   |	Random random = new Random()
	 * 		   |	boolean teleported = false
	 * 		   | In
	 * 		   |	while !teleported
	 * 		   |		Position newPos = new Position(random.nextLong()%robot.getBoard().getWidth(), random.nextLong()%robot.getBoard().getHeight())
	 * 		   |		boolean canShare = true
	 * 		   |		for each element in robot.getBoard().getElements(newPos)
	 * 		   |			if !robot.canSharePositionWith(element)
	 * 		   |			then canShare = false
	 * 		   |		if canShare
	 * 		   |		then robot.setBoardPosition(robot.getBoard(), newPos)
	 * 		   |			teleported = true
	 */
	@Model
	private void eventTeleport(Robot robot){
		boolean teleported = false;
		Random random = new Random();
		
		while(!teleported) {
			Position newPos = new Position(Math.abs(random.nextLong()%robot.getBoard().getWidth()), Math.abs(random.nextLong()%robot.getBoard().getHeight()));
			boolean canShare = true;
			for(Element element : robot.getBoard().getElements(newPos)) {
				if(!robot.canSharePositionWith(element)) {
					canShare = false;
				}
			}
			if(canShare) {
				robot.setBoardPosition(robot.getBoard(), newPos);
				teleported = true;
			}
		}
		
		
	}

	/**
	 * Creates a random item, and adds it to the robot's possessions (the robot should be the robot that's holding this surprise box).
	 * 
	 * @param robot The given robot.
	 * 
	 * @post ...
	 * 		 | (new robot).getNbItems() = robot.getNbItems() +1
	 */
	@Model
	private void eventRandomItem(Robot robot){
		Random random = new Random();
		int num = random.nextInt(3);
		Item item;
		if(num == 0) {
			item = new Battery(5000, 50);
		}
		if(num == 1) {
			item = new RepairKit(1000, 5000);
		}
		else {
			item = new SurpriseBox(100);
		}
		item.setBoardPosition(robot.getBoard(), robot.getPosition());
		robot.pickUp(item);
	}

	/**
	 * Invoked when this item takes a lasershot.
	 * 
	 * @effect ...
	 * 		   this.explode()
	 */
	@Override
	public void takeShot() {
		explode();
	}

	/**
	 * Explode when hit by a laser, hitting all items in the adjacent squares.
	 * @effect ...
	 * 		   | for each orientation in Orientation.values()
	 * 		   | 	for each Item item in getBoard().getElements(getPosition().getNeighbour(orientation))
	 * 		   | 		item.takeShot()
	 */
	@Model
	private void explode(){
		for(Orientation orientation : Orientation.values()){
			for(Element element : getBoard().getElements(getPosition().getNeighbour(orientation))) {
				if(Item.class.isAssignableFrom(element.getClass())) {
					element.takeShot();
				}
			}
		}
		terminate();
	}

	/**
	 * Clone this surpriseBox.
	 * 
	 * @return ...
	 * 		   | (result.getBoard() == this.getBoard() && result.getPosition() == this.getPosition())
	 * @return ...
	 * 		   | result.getWeight() == this.getWeight()
	 */
	@Override
	public Item clone() {
		SurpriseBox clone = new SurpriseBox(getWeight());
		clone.setBoardPosition(getBoard(), getPosition());
		return clone;
	}

	@Override
	public String toString() {
		return "SB" + getPosition().toString();
	}

}
